// ##
#include "OdbcStatementExecutor.h"
// ## ODBC
#include <sql.h>
#include <sqlext.h>
// ## C++
#include <cassert>
#include <iostream>

namespace jmlib {
	
	SQLTCHAR* OdbcStatementExecutor::CONNECTION_STRING = 
		(SQLTCHAR*) TEXT("DSN=Diplomka_1;");

	int initColumns(OdbcStatementExecutor::ColumnSQLTypes& types, 
		SQLHANDLE sqlstatementhandle) {
			// detekcia poctu stlpcov
			SQLSMALLINT columns = 0;
			SQLNumResultCols(sqlstatementhandle, &columns);
			// detekcia datovych typov stlpcov

			for (SQLSMALLINT i = 0; i < columns; ++i) {
				const int COL_LENGHT = 64;
				SQLTCHAR colName[COL_LENGHT];
				SQLSMALLINT nameLenghtPtr = 0, dataTypetPtr = 0,
					decimalDigitsPtr = 0, nullablePtr = 0;
				SQLULEN columnSizePtr;

				SQLDescribeCol(sqlstatementhandle, i  + 1, colName, 
					COL_LENGHT, &nameLenghtPtr, &dataTypetPtr, &columnSizePtr, 
					&decimalDigitsPtr, &nullablePtr);


				types.push_back((int)dataTypetPtr);// Ulozim typ stlpca
//				data.addColumn();//push_back(ColumnType());// zalozim strukturu pre stlpec
			}

			return columns;
	}

	template<int columnType, class T, int size>
	void load(int columnIndex, StatementExecutor::DataTarget* target,
		SQLHANDLE sqlstatementhandle) {
			T value;
			SQLGetData(sqlstatementhandle, columnIndex+1, columnType, &value, 
				size, NULL);
			target->addValue(columnIndex, value);
	}

	void loadColumn(size_t colIndex, 
		const OdbcStatementExecutor::ColumnSQLTypes& sqlTypes, 
		StatementExecutor::DataTarget* target, SQLHANDLE sqlstatementhandle) { 

			assert(colIndex < sqlTypes.size());

			switch (sqlTypes[colIndex]) {
			case SQL_VARCHAR:		
				load<SQL_CHAR, OdbcStatementExecutor::StringDataType, 
					OdbcStatementExecutor::STRING_DATA_SIZE>(colIndex, target,
						sqlstatementhandle);
				break;
			case SQL_INTEGER:
				load<SQL_INTEGER, OdbcStatementExecutor::IntegerDataType, 
					OdbcStatementExecutor::INTEGER_DATA_SIZE>(colIndex, target,
						sqlstatementhandle);
				break;
			//case SQL_VARCHAR:
			//	load<SQL_VARCHAR, OdbcStatementExecutor::StringDataType, 
			//		OdbcStatementExecutor::STRING_DATA_SIZE>(colIndex, target,
			//		sqlstatementhandle);
			//	break;
			default:
				assert(false); 
			}
	}

	void show_error(unsigned int handletype, const SQLHANDLE& handle){
		SQLTCHAR sqlstate[1024];
		SQLTCHAR message[1024];
		if(SQL_SUCCESS == SQLGetDiagRec(handletype, handle, 1, sqlstate, NULL, message, 1024, NULL))
			std::cerr <<"Message: "<<message<<"\nSQLSTATE: "<<sqlstate<<std::endl;
	}

	bool OdbcStatementExecutor::initEnviroment() {
		return SQL_SUCCESS == SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, 
			&enviromentHandle_) && SQL_SUCCESS == 
			SQLSetEnvAttr(enviromentHandle_, SQL_ATTR_ODBC_VERSION, 
			(SQLPOINTER)SQL_OV_ODBC3, 0);

	}

	bool OdbcStatementExecutor::connect() {

		auto retAllocHandle = SQLAllocHandle(SQL_HANDLE_DBC, enviromentHandle_, 
			&connectionHandle_);

		if (retAllocHandle != SQL_SUCCESS) return false;
		//SQLCHAR retconstring[1024];

		auto connect = SQLDriverConnect(connectionHandle_, NULL, 
			CONNECTION_STRING, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);

		switch(connect) {
		case SQL_SUCCESS_WITH_INFO:
			show_error(SQL_HANDLE_DBC, connectionHandle_);
			return true;
		case SQL_INVALID_HANDLE:
		case SQL_ERROR:
			show_error(SQL_HANDLE_DBC, connectionHandle_);
			return false;
		default:
			break;
		}

		return true;
	}

	bool OdbcStatementExecutor::sendStatement(const std::string& s) {
#ifdef UNICODE
		std::wstring tmp;
		tmp.assign(s.begin(), s.end());
		LPCTSTR statement = tmp.c_str();
#else 
		LPCTSTR statement = s.c_str();
#endif

		return SQL_SUCCESS == SQLAllocHandle(SQL_HANDLE_STMT, connectionHandle_, 
			&statementHandle_) && SQL_SUCCESS == SQLExecDirect(statementHandle_, 
			(SQLTCHAR*)statement, SQL_NTS);
	}


	void OdbcStatementExecutor::execute(const std::string& statement, 
		DataTarget* target) {
			if (initEnviroment()) {
				if (connect()) {
					if (sendStatement(statement)) {
						ColumnSQLTypes types;
						int cols = initColumns(types, statementHandle_);
						while(SQLFetch(statementHandle_)==SQL_SUCCESS){
							for (SQLSMALLINT i = 0; i < cols; ++i) {
								loadColumn(i ,types, target, statementHandle_);
							}
						}
					}
				}
			}

			releaseSQLHandles();
	}

	void OdbcStatementExecutor::releaseSQLHandles() {
		SQLFreeHandle(SQL_HANDLE_STMT, statementHandle_ );
		SQLDisconnect(connectionHandle_);
		SQLFreeHandle(SQL_HANDLE_DBC, connectionHandle_);
		SQLFreeHandle(SQL_HANDLE_ENV, enviromentHandle_);
	}


} // namespace jmlib
